Fork me on GitHub

A symbol table holds references. It is simply a Map at its core but it provides type and exception handling functionality. The @SuppressWarnings("unchecked") stops the compiler from incorrectly warning about unchecked casts as the check is done via reflection and the compiler isn’t convinced that it has been checked.

@SuppressWarnings("unchecked")
public class SymbolTable {

The declarations are the actually name -> value mappings.

    private Map<String, Object> declarations;

    public SymbolTable() {
        declarations = new HashMap<>();
    }

addDeclaration is the only data input method for this class. It adds a new mapping for the identifier to the value. It will overwrite an existing data and will return true if it does so.

    public boolean addDeclaration(String identifier, Object value) {
        return this.declarations.put(identifier, value) != null;
    }

getDeclarationValue is the based data output method for this class. It takes the name of the identifier to lookup and the expected type. If the value does not exist this method simply returns null. If the value exists but is the wrong type this method will treat the identifier as non-existent and return null. Otherwise the value is returned.

    public <T> T getDeclarationValue(String identifier, Class<T> type) {
        Object value = declarations.get(identifier);
        return type.isInstance(value) ? (T) value : null;
    }

getDeclarationValueOrThrow is similar in function to getDeclarationValue but instead of returning null, the appropriate exception will be thrown.

    public <T> T getDeclarationValueOrThrow(Token token, Class<T> type) {
        Object value = declarations.get(token.getText());

If the value is null then there is nothing defined so throw an UndefinedReferenceException.

        if (value == null)
            throw new UndefinedReferenceException(token, "Identifier ("+token.getText()+") is undefined.");

If the type of the value is incorrect then something is defined but it is the wrong type so throw an IncorrectTypeException.

        if (!type.isInstance(value))
            throw new IncorrectTypeException(token, "Identifier ("+token.getText()+") points to a "+value.getClass().getSimpleName()+" not a "+type.getSimpleName());

Otherwise all is fine so we can safely cast and return the value

        return (T) value;
    }

getType is a utility method that will return the type of the data. This will return null if the identifier is not defined.

    public Class<?> getType(String identifier) {
        Object value = declarations.get(identifier);
        return value == null ? null : value.getClass();
    }

identifierTypeIs preforms a type check on the identifier. It will return true if the identifier is defined and points to an object that can safely be cast to type

    public boolean identifierTypeIs(String identifier, Class<?> type) {
        Object value = declarations.get(identifier);
        return value != null && type.isAssignableFrom(value.getClass());
    }

checkType is the equivalent of identifierTypeIs throwing an exception if the type is defined and incorrect (not able to be cast to type).

    public void checkType(Token token, Class<?> type) {
        Object value = declarations.get(token.getText());
        if (value != null && !type.isAssignableFrom(value.getClass()))
            throw new IncorrectTypeException(token, "Identifier (" + token.getText() + ") points to a " + value.getClass().getSimpleName() + " not a " + type.getSimpleName());
    }

checkExists is a utility method that throws an exception if the identifier that the token points to is not defined.

    public void checkExists(Token token) {
        Object value = declarations.get(token.getText());
        if (value == null)
            throw new UndefinedReferenceException(token, "Identifier ("+token.getText()+") is undefined.");
    }
}
h